package com.itcam.project.manager;

import cn.hutool.core.collection.CollUtil;
import com.itcam.project.config.CosClientConfig;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.model.*;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.qcloud.cos.transfer.Download;
import com.qcloud.cos.transfer.TransferManager;
import org.checkerframework.checker.units.qual.C;
import org.springframework.stereotype.Component;

/**
 * Cos 对象存储操作
 *
 */
@Component
public class CosManager {

    @Resource
    private CosClientConfig cosClientConfig;

    @Resource
    private COSClient cosClient;

    /**
     * 上传对象到云存储（如腾讯云COS）
     *
     * @param key 唯一键（类似文件名，在存储桶中唯一标识对象）🗝️
     * @param localFilePath 本地文件路径（要上传的源文件位置）📁
     * @return 上传结果（包含ETag等元数据信息）📦
     */
    public PutObjectResult putObject(String key, String localFilePath) {
        // 1️⃣ 创建上传请求对象：就像填写快递单（指定收件地址、包裹名、寄件物品）
        PutObjectRequest putObjectRequest = new PutObjectRequest(
                cosClientConfig.getBucket(), // 存储桶名（类似快递的收件地址）
                key,                         // 对象键（类似包裹名称）
                new File(localFilePath)      // 本地文件（要寄出的物品）
        );

        // 2️⃣ 执行上传操作：就像快递员揽件后发往目的地
        return cosClient.putObject(putObjectRequest); // 调用COS客户端发送请求
    }

    /**
     * 上传对象
     *
     * @param key 唯一键，用于在存储桶中唯一标识该对象，类似于文件名
     * @param file 文件，即要上传到存储桶的实际文件对象
     * @return 上传结果，包含上传对象的相关元数据信息，如 ETag 等
     */
    public PutObjectResult putObject(String key, File file) {
        // 1️⃣ 创建一个 PutObjectRequest 对象，指定存储桶名称、对象键和文件对象
        // 这里的 cosClientConfig.getBucket() 用于获取配置好的存储桶名称
        // key 是对象的唯一标识，file 是要上传的文件
        PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
                file);
        // 2️⃣ 使用 COSClient 上传对象，并返回上传结果
        // 调用 cosClient 的 putObject 方法，将之前创建的请求对象传入，完成上传操作
        return cosClient.putObject(putObjectRequest);
    }

    /**
     * 下载对象
     * 就好比去大仓库取你的快递包裹
     * @param key 唯一键，相当于快递单号，用来在仓库里精准找到你的包裹
     * @return COSObject 对象，也就是你取到的包裹
     */
    public COSObject getObject(String key) {
        // 1️⃣ 创建一个 GetObjectRequest 对象，指定存储桶名称和对象键
        // 这一步就像是你填了一张取件单，写上了仓库地址（存储桶名称）和单号（对象键）
        GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
        // 2️⃣ 使用 COSClient 下载对象，并返回 COSObject 对象
        // 这一步就好比你把取件单交给快递员，然后快递员把包裹拿给你，你把包裹返回
        return cosClient.getObject(getObjectRequest);
    }

    // 1️⃣ 复用对象，用于后续的多线程文件上传下载管理
    private TransferManager transferManager;

    // 2️⃣ bean 加载完成后执行此方法，进行初始化操作
    @PostConstruct
    public void init() {
        // 3️⃣ 执行初始化逻辑，打印提示信息，实际开发建议用日志框架
        System.out.println("Bean initialized!");
        // 4️⃣ 创建一个固定大小为 32 的线程池，用于多线程并发操作
        ExecutorService threadPool = Executors.newFixedThreadPool(32);
        // 5️⃣ 初始化 TransferManager，关联 COS 客户端和线程池
        transferManager = new TransferManager(cosClient, threadPool);
    }

    /**
     * 下载对象到本地文件
     * 就像从图书馆借一本书并带回家
     * @param key 要下载对象的唯一键，相当于书的编号，用于在存储桶中定位对象
     * @param localFilePath 本地文件路径，相当于家里准备放书的书架位置
     * @return 下载对象，包含下载操作的相关信息
     * @throws InterruptedException 当线程在等待下载完成时被中断，抛出此异常
     */
    public Download download(String key, String localFilePath) throws InterruptedException {
        // 1️⃣ 创建本地文件对象，指定下载文件的存储位置
        File downloadFile = new File(localFilePath);
        // 2️⃣ 创建下载请求对象，指定存储桶名称和对象键
        GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
        // 3️⃣ 发起下载操作，将请求对象和本地文件对象传入
        Download download = transferManager.download(getObjectRequest, downloadFile);
        // 4️⃣ 同步等待下载完成，就像在图书馆等待拿到书
        download.waitForCompletion();
        // 5️⃣ 返回下载对象，包含下载操作的相关信息
        return download;
    }

    /**
     * 删除对象
     *
     * @param key 要删除对象的唯一键，用于标识存储在对象存储中的特定对象
     * @throws CosClientException 当客户端与对象存储服务通信时发生错误，例如网络问题、认证失败等，抛出此异常
     * @throws CosServiceException 当对象存储服务端出现问题，例如服务不可用、请求参数错误等，抛出此异常
     */
    public void deleteObject(String key) throws CosClientException, CosServiceException {
        // 调用 COSClient 的 deleteObject 方法，传入存储桶名称和对象键，以删除指定对象
//        cosClient.deleteObject(cosClientConfig.getBucket(), key);
        Optional.ofNullable(key)
                .filter(k->k.isEmpty())
                .orElseThrow(()->{
                    return new IllegalArgumentException("对象键不能为空");
                });
        cosClient.deleteObject(cosClientConfig.getBucket(),key);
    }

    /**
     * 批量删除对象
     */
    public DeleteObjectsResult deleteObjects(List<String> keyList)
            throws MultiObjectDeleteException, CosClientException, CosServiceException {
        // 1️⃣ 创建批量删除请求对象
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());

        // 2️⃣ 初始化待删除Key列表容器（最多支持1000个）
        ArrayList<DeleteObjectsRequest.KeyVersion> keyVersions = new ArrayList<>();

        // 3️⃣ 遍历输入Key列表，转换为SDK需要的KeyVersion格式
        for (String key : keyList) {
            // 4️⃣ 注意：Key不能以/或\开头（示例："a/b.txt"有效，"/a/b.txt"无效）
            keyVersions.add(new DeleteObjectsRequest.KeyVersion(key));
        }

        // 5️⃣ 将待删除Key列表设置到请求对象中
        deleteObjectsRequest.setKeys(keyVersions);

        // 6️⃣ 调用COS客户端执行批量删除操作
        DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);

        // 7️⃣ 返回包含成功/失败明细的结果对象
        return deleteObjectsResult;
    }

    /**
     * 删除目录
     *
     * @param delPrefix 要删除目录的前缀，用于指定要删除的目录范围
     * @throws CosClientException 当客户端与对象存储服务通信时发生错误，例如网络问题、认证失败等，抛出此异常
     * @throws CosServiceException 当对象存储服务端出现问题，例如服务不可用、请求参数错误等，抛出此异常
     */
    public void deleteDir(String delPrefix) throws CosClientException, CosServiceException {
        // 创建一个 ListObjectsRequest 对象，用于列出存储桶中的对象
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        // 设置要操作的存储桶名称
        listObjectsRequest.setBucketName(cosClientConfig.getBucket());
        // 设置列出对象的前缀，即只列出以 delPrefix 开头的对象
        listObjectsRequest.setPrefix(delPrefix);
        // 设置每次列出对象的最大数量，一次 listobject 最大支持 1000 个
        listObjectsRequest.setMaxKeys(1000);

        // 用于保存每次列出对象的结果
        ObjectListing objectListing;

        do {
            // 调用 COSClient 的 listObjects 方法，列出符合条件的对象
            objectListing = cosClient.listObjects(listObjectsRequest);
            // 获取列出的对象摘要列表
            List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
            // 如果对象摘要列表为空，说明没有符合条件的对象，跳出循环
            if (CollUtil.isEmpty(cosObjectSummaries)) {
                break;
            }

            // 用于保存要删除的对象的键信息
            ArrayList<DeleteObjectsRequest.KeyVersion> delObjects = new ArrayList<>();
            // 遍历对象摘要列表，将每个对象的键添加到 delObjects 列表中
            for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
                delObjects.add(new DeleteObjectsRequest.KeyVersion(cosObjectSummary.getKey()));
            }

            // 创建一个 DeleteObjectsRequest 对象，用于批量删除对象
            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());
            // 设置要删除的对象的键列表
            deleteObjectsRequest.setKeys(delObjects);
            // 调用 COSClient 的 deleteObjects 方法，批量删除对象
            cosClient.deleteObjects(deleteObjectsRequest);

            // 获取下一次列出对象的起始标记
            String nextMarker = objectListing.getNextMarker();
            // 设置下一次列出对象的起始标记
            listObjectsRequest.setMarker(nextMarker);
            // 如果结果被截断，说明还有更多对象需要列出，继续循环
        } while (objectListing.isTruncated());
    }


}

